﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using RoketSolutions.SP2010.Infrostructure.UI.Ajax.Abstractions;
using RoketSolutions.SP2010.Infrostructure.UI.Ajax.ActionResults;

namespace RoketSolutions.SP2010.Infrostructure.UI.Ajax.Controllers
{
    public class ControllerActionInvoker
    {
        static readonly Cache<Type, ControllerDescriptor> descriptorCache = new Cache<Type, ControllerDescriptor>();

        public virtual bool InvokeControllerAction(ControllerContext context, String actionName)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (String.IsNullOrEmpty(actionName))
                throw new ArgumentNullException("actionName");

            var controllerDescriptor = GetControllerDescriptor(context);
            var actionDescriptor = GetActionDescriptor(context, controllerDescriptor, actionName);

            if (actionDescriptor != null)
            {
                var parameters = GetParameterValues(context, actionDescriptor);
                var actionResult = InvokeControllerAction(context, actionDescriptor, parameters);
                actionResult.Execute(context);
                return true;
            }
            return false;
        }

        protected virtual ActionResult InvokeControllerAction(
            ControllerContext context, ActionDescriptor actionDescriptor, IDictionary<String, Object> parameters)
        {
            var returnValue = actionDescriptor.Execute(context, parameters);
            return CreateActionResult(context, actionDescriptor, returnValue);
        }

        protected virtual ActionResult CreateActionResult(
            ControllerContext context, ActionDescriptor actionDescriptor, Object returnValue)
        {
            if (returnValue == null)
                return new EmptyResult();

            var actionResult = (returnValue as ActionResult) ??
                               new ContentResult(Convert.ToString(returnValue, CultureInfo.InvariantCulture));
            return actionResult;
        }

        ControllerDescriptor GetControllerDescriptor(ControllerContext context)
        {
            var controllerType = context.Controller.GetType();
            var controllerDescriptor = DescriptorCache.GetEntry(
                controllerType,
                () => new ControllerDescriptor(context.Controller));
            return controllerDescriptor;
        }

        static ActionDescriptor GetActionDescriptor(
            ControllerContext context, ControllerDescriptor controllerDescriptor, String actionName)
        {
            return controllerDescriptor.FindActionMethod(actionName);
        }

        static IDictionary<String, Object> GetParameterValues(
            ControllerContext context, ActionDescriptor actionDescriptor)
        {
            return actionDescriptor.GetParameters().ToDictionary(
                parameter => parameter.Name, parameter => GetParameterValue(context, parameter));
        }

        static object GetParameterValue(ControllerContext context, ParameterInfo parameter)
        {
            var name = parameter.Name;
            var parameterType = parameter.ParameterType;
            if (parameterType.IsArray)
            {
                //jquery 1.4.x+ workaround for arrays
                var value = context.HttpContext.Request[name + "[]"];
                value = value ?? context.HttpContext.Request[name];
                if (!string.IsNullOrEmpty(value))
                    return ConvertArray(value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), parameterType);
            }
            var parameterValue = context.HttpContext.Request[name];
            return ConvertParameterValue(parameterValue, parameterType);
        }

        static object ConvertArray(object value, Type destinationType)
        {
            if ((value == null) || destinationType.IsInstanceOfType(value))
            {
                return value;
            }
            var array = value as Array;
            if (destinationType.IsArray)
            {
                Type elementType = destinationType.GetElementType();
                if (array != null)
                {
                    IList list = Array.CreateInstance(elementType, array.Length);
                    for (int i = 0; i < array.Length; i++)
                    {
                        list[i] = ConvertParameterValue(array.GetValue(i), elementType);
                    }
                    return list;
                }
                //if just a single item
                object parameterValue = ConvertParameterValue(value, elementType);
                IList singleItemList = Array.CreateInstance(elementType, 1);
                singleItemList[0] = parameterValue;
                return singleItemList;
            }
            return null;
        }

        static object ConvertParameterValue(object value, Type destinationType)
        {
            if ((value == null) || destinationType.IsInstanceOfType(value))
            {
                return value;
            }

            var typeConverter = TypeDescriptor.GetConverter(destinationType);
            if (typeConverter != null && typeConverter.CanConvertFrom(value.GetType()))
            {
                return typeConverter.ConvertFrom(null, CultureInfo.CurrentCulture, value);
            }
            return null;
        }

        public Cache<Type, ControllerDescriptor> DescriptorCache
        {
            get { return descriptorCache; }
        }
    }
}